/***********************************************************************************\
							SIMPLE 3D BOWLING GAME
								Blair Trusler

Using OpenGL and GLUT (OpenGL Utility Toolkit- window system independent toolkit for writing OpenGL programs)

-------------------------------------------------------------------------------------
CONTROLS:
Left Arrow - Move ball left along lane
Right Arrow - Move ball right along lane
Up Arrow - Increase angle at which ball will be bowled
Down Arrow - Decrease angle at which ball will be bowled
Space Bar - Bowl ball
R/r Keys - Reset game
ESC - Exit program
-------------------------------------------------------------------------------------

Possible features to improve game (given time):
-Gradient on bowling lane floor and physics on ball to create realistic ball movement.
-"Power" of bowl and how that impacts hitting pins.

\***********************************************************************************/


/***********************************************************************************\ 
								INCLUDE HEADERS
\***********************************************************************************/

#include <GL/glut.h>
#include <GL/gl.h>

/***********************************************************************************\ 
							WINDOWS DEFINITIONS AND VARIABLES
\***********************************************************************************/

#define WIN_WIDTH  550		//Frame width is 550 pixels 
#define WIN_HEIGHT 750		//Frame height is 750 pixels

/***********************************************************************************\ 
							GAME DEFINITIONS AND VARIABLES
\***********************************************************************************/

const float LANE_WIDTH = 1.25;			//Width of bowling lane
const float LANE_LENGTH = 18.0f;		//Length of bowling lane

const int NUM_PINS = 10;				//Number of pins
const int MAX_NUM_OF_BALLS = 2;			//Maximum number of balls player can bowl
int NUM_OF_BALLS_BOWLED;				//Number of balls player has bowled
bool hasBowled = false;					//Determines whether ball can be aligned along X axis (or has already been bowled)
int NUM_OF_PINS_HIT;					//Tracks number of pins hit by ball
bool gameOver;							//Determines whether game is over or not

float ballX = 0.0f;						//Ball position on X axis
float ballY = 0.25f;					//Ball position on Y axis
float ballZ = 1.0f;						//Ball position on Z axis
double ballSpeedX=0.05;					//Speed at which ball can be moved along X axis
double ballSpeedZ=0.008;					//Speed at which ball bowls down lane along Z axis
float ballAngle = 0;					//Angle at which ball will be bowled
const float MAX_BALL_ANGLE = 0.005;		//Max angle at which ball can be bowled

float angle=0.0;						//Angle of rotation for the camera direction
float lx=0.0f, lz=-1.0f;				//Vector representing the camera's direction
float x=0.0f, z=5.0f;					//X,Z position of the camera

void *font = GLUT_BITMAP_HELVETICA_18;	//Font set to Helvetica, Size 18

/***********************************************************************************\ 
							FUNCTION PROTOTYPES
\***********************************************************************************/

void resetGame();		//Reset game to initial settings
void resetBall();		//Reset ball position

void drawBowlingBall(float positionx, float positiony, float positionz);		//Draw bowling ball at X,Y,Z positions
void drawPins();		//Draw rows of pins (total of 10 pins)
void initPinSize();		//Initialize the size of pins
void removePin(int i);	//Remove pins from scene
void drawLane();		//Draw bowling lane environment
void setCamera();		//Set camera position/angle
void setText();			//Set text to be displayed on-screen
void renderBitmapString(float x, float y, float z, void *font, char *string);	//Render text set in setText() function
void renderScene();		//Render whole scene

void bowlBall();		//Bowl the ball down the lane
void resetBall();		//Reset ball position (x and/or z axis)
void newGame();			//Reset game to starting state

void processNormalKeys(unsigned char key, int x, int y);	//Handles keyboard input		
void processSpecialKeys(int key, int xx, int yy);			//Handles keyboard input

void createSubMenu();					//Creates submenu on right-click
void processMenuEvents(int option);		//Handles submenu events

void changeSize(int w, int h);			//Handles window re-sizing

/***********************************************************************************\ 
							BALL & PIN STRUCTS
\***********************************************************************************/

//Bowling Ball
struct ball{
	float r;			//Radius of ball
	float slice;		//Number of subdivisions around Z axis (longitude)
	float stack;		//Number of subdivisions around Z axis (latitude)
	float x,y,z;		//Coordinates for ball position
}ball0;

//Pin
struct pin{
	float x1,y1,x2,y2;	//Coordinates for drawing rectangle
	float x,y,z;		//Coordinates for pin position
	bool show;			//Determines whether to draw pin or not
}pin[NUM_PINS];


class Ball1{
public: 
	float r, slice, stack, x, y, z;

};
	
/***********************************************************************************\ 
							DRAW & RENDER OBJECTS
				Draws bowling ball, pins, environment, text
\***********************************************************************************/

//Draw bowling ball at X,Y,Z positions:
void drawBowlingBall(float positionx, float positiony, float positionz){
	ball0.r = 0.25f;						//Set ball radius
	ball0.slice = 20.0f;					//Set ball slice
	ball0.stack = 20.0f;					//Set ball stack
	ball0.x = positionx;					//Set ball position on x axis
	ball0.y = positiony;					//Set ball position on y axis
	ball0.z = positionz;					//Set ball position on z axis
	glColor3f(0.5f, 0.0f, 0.7f);			//Set ball colour to white
	glTranslatef(positionx,positiony,positionz);		//Translate position onto virtual space
	glutSolidSphere(ball0.r,ball0.slice,ball0.stack);	//Generate ball as sphere

}

//Draw rows of pins (total of 10 pins):
void drawPins(){
	glLoadIdentity();
	for (int i = 0; i < 4; i++){					//Draw pins on row 4
		if (pin[i].show){
			glPushMatrix();
			glColor3f(1.0f, 1.0f, 1.0f);	
			pin[i].x = i-1.5f;
			pin[i].y = -0.5f;
			pin[i].z = -20.0f;
			glTranslatef(pin[i].x, pin[i].y,pin[i].z);
			glRectf(pin[i].x1,pin[i].y1, pin[i].x2, pin[i].y2);
			glPopMatrix();
		}
		else if (!pin[i].show){
			removePin(i);							//If pin is set to hidden, remove pin
		}

			for (int i = 4; i < 7; i++){			//Draw pins on row 3
				if (pin[i].show){
					glPushMatrix();
					glColor3f(0.5f, 1.0f, 1.0f);
					pin[i].x = i-5.0f;
					pin[i].y = -0.5f;
					pin[i].z = -18.0f;
					glTranslatef(pin[i].x, pin[i].y,pin[i].z);
					glRectf(-0.25f,0.35f, 0.25f, -0.65f);
					glPopMatrix();
				}
				else if (!pin[i].show){
					removePin(i);					//If pin is set to hidden, remove pin
				}

				for (int i = 7; i < 9; i++){		//Draw pins on row 2
					if (pin[i].show){
						glPushMatrix();
						glColor3f(0.5f, 0.5f, 1.0f);
						pin[i].x = i-7.5f;
						pin[i].y = -0.5f;
						pin[i].z = -16.0f;
						glTranslatef(pin[i].x, pin[i].y,pin[i].z);
						glRectf(-0.25f,0.35f, 0.25f, -0.65f);
						glPopMatrix();
					}
					else if (!pin[i].show){
						removePin(i);				//If pin is set to hidden, remove pin
					}

					for (int i = 9; i < 10; i++){	//Draw pin on row 1
						if (pin[i].show){
							glPushMatrix();
							glColor3f(1.0f, 0.0f, 0.0f);
							pin[i].x = i-9.0f;
							pin[i].y = -0.5f;
							pin[i].z = -14.0f;
							glTranslatef(pin[i].x, pin[i].y,pin[i].z);
							glRectf(-0.25f,0.35f, 0.25f, -0.65f);
							glPopMatrix();
						}
						else if (!pin[i].show){
							removePin(i);			//If pin is set to hidden, remove pin
						}
					}
				}
			}
	}
	glEnd();
}

//Initialize the size of pins:
void initPinSize(){
	for (int i = 0; i < NUM_PINS; i++){
		pin[i].x1 = -0.25f;
		pin[i].x2 = 0.25;
		pin[i].y1 = 0.35f;
		pin[i].y2 = -0.65;
		pin[i].show = true;			//Set pin to visible
	}
}

//Remove pins from scene:
void removePin(int i){
		glPushMatrix();
		glTranslatef(pin[i].x, pin[i].y,pin[i].z);
		glRectf(0.0,0.0, 0.0, 0.0);
		glPopMatrix();
}

//Draw the bowling lane environment (floor & walls):
void drawLane(){
	glBegin(GL_QUADS);
		//Floor:
		glColor3f(0.5f, 0.35f, 0.05f);			//Set colour to brown
		glVertex3f(-100.0f, 0.0f, -100.0f);		//Set X,Y,Z position
		glVertex3f(-100.0f, 0.0f,  100.0f);
		glVertex3f( 100.0f, 0.0f,  100.0f);
		glVertex3f( 100.0f, 0.0f, -100.0f);
		
		//Side Walls:
		glColor3f(0.5f, 0.5f, 0.5f);			//Set colour to grey
		glVertex3f(2,1.5,-100); 
		glVertex3f(2,-1.5,1); 
		glVertex3f(2,-1.5,-1); 
		glVertex3f(2,1.5,-1);

		glVertex3f(-2,1.5,-100);
		glVertex3f(-2,-1.5,1);
		glVertex3f(-2,-1.5,-1);
		glVertex3f(-2,1.5,-1);

		//Back Wall:
        glVertex3f(2.0f, 1.5f, -30.0f);
        glVertex3f(-2.0f, 1.5f, -30.0f);
        glVertex3f(-2.0f, -1.5f, -30.0f);
		glVertex3f(2.0f, -1.5f, -30.0f);
	glEnd();
}

//Set camera position/angle:
void setCamera(){
	gluLookAt(	
		x, 1.0f, z,	
		x+lx, 1.0f,  z+lz,
		0.0f, 1.0f,  0.0f);
}

//Set information text to be displayed on-screen:
void setText(){
	glColor3f(1.0f, 1.0f, 1.0f);												//Set font colour to white

	char numBowled[2];
	char numBowledText[7] = "Balls:";
	itoa(MAX_NUM_OF_BALLS-NUM_OF_BALLS_BOWLED, numBowled,10);
	renderBitmapString(-1.65f, 3.15f, -1.0f, (void *)font , numBowledText);		//Render string with text
	renderBitmapString(-1.25f, 3.15f, -1.0f, (void *)font , numBowled);			//Render string with number of balls remaining

	char numPins[20];
	char numPinsText[6] = "Pins:";
	itoa(NUM_OF_PINS_HIT, numPins, 12);
	renderBitmapString(-1.65f, 2.90f, -1.0f, (void *)font , numPinsText);		//Render string with text
	renderBitmapString(-1.25f, 2.90f, -1.0f, (void *)font , numPins);			//Render string with number of pins remaining


	if (gameOver){																//If game is over, display additional text												
		renderBitmapString(-0.4f, 3.15f, -1.0f, (void *)font , "Game Over!");
		renderBitmapString(-0.5f, 2.90f, -1.0f, (void *)font , "Press R to Reset");
	}

}

//Render text set in setText() function:
void renderBitmapString(float x, float y, float z, void *font, char *string) {
	char *c;
	glRasterPos3f(x, y,z);
	for (c=string; *c != '\0'; c++) {
		glutBitmapCharacter(font, *c);
	}
}

//Render whole scene:
void renderScene(void) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		//Clear Color and Depth Buffers

	glLoadIdentity();						//Reset transformations
	setCamera();							//Sets up camera
	setText();								//Sets up on-screen text
	drawLane();								//Draws bowling lane
	drawBowlingBall(ballX,ballY,ballZ);		//Draws bowling ball
	drawPins();								//Draws pins

	glutSwapBuffers();						//Swap the window buffers
}

/***********************************************************************************\ 
								GAME FEATURES
						eg Bowl ball, New game etc
\***********************************************************************************/

//Bowl the ball down the lane
void bowlBall(){
	hasBowled = true;						//Ball is currently bowling
	NUM_OF_BALLS_BOWLED++;					//Used up another bowling ball

	while (ballZ>-LANE_LENGTH){				//While ball has not reached end of lane, keep moving 

		if ((ballX>=1.25)||(ballX<=-1.25)){	//If ball hits walls, roll along the walls
			ballAngle=0;					//Reset ball angle to 0
			ballZ-=ballSpeedZ;				//Move ball along -Z axis towards pins at speed ballSpeedZ
			renderScene();					//Render scene to show movement
		}
		else{
			ballZ-=ballSpeedZ;					//Move ball along -Z axis towards pins at speed ballSpeedZ
			ballX+=ballAngle;					//Add ball angle along X axis (as set by user with Up/Down Arrow keys
			renderScene();						//Render scene to show movement
		}

		for (int i = 0; i < NUM_PINS; i++){			
			if ((ball0.z<=(pin[i].z+5.5))&&(ball0.z>=(pin[i].z+5.49))){				//Checking for pin position on Z axis compared to ball
				if (((ball0.x>=(pin[i].x-0.4)))&&(ball0.x<=(pin[i].x+0.4))){		//Checking for pin position on X axis compared to ball
					if (pin[i].show){
						!pin[i].show;
						pin[i].show = false;		//If pin is hit, set "show" bool to false
						if (!pin[i].show){
							NUM_OF_PINS_HIT+=1;		//Register number of pins hit (outputted to text on-screen)
						}
					}

				}
			}

		}
	}
	resetBall();			//Reset ball position
}

//Reset ball position:
void resetBall(){
	if (NUM_OF_BALLS_BOWLED < MAX_NUM_OF_BALLS){		//Reset to original position on X and Z axis
		hasBowled=false; 
		ballZ+=19.0f;	
		ballX=0.0f;	
	}
	else if (NUM_OF_BALLS_BOWLED <= MAX_NUM_OF_BALLS){	//Run out of balls to bowl
		hasBowled=true;
		gameOver = true;
		gameOver;
	}
}

//Reset game to starting state:
void newGame(){
	gameOver = false;
	hasBowled=false;
	NUM_OF_BALLS_BOWLED = 0;
	ballX=0.0f;	
	ballZ=1.0f;
	for (int i = 0; i < NUM_PINS; i++){
		if (!pin[i].show){
			NUM_OF_PINS_HIT=0;
		}
	}
	for (int i=0; i<NUM_PINS; i++){	//Resets pins
		pin[i].show = true;
	}	
	renderScene();
}

/***********************************************************************************\ 
						KEYBOARD INPUT HANDLING 

	Active Keys : ESC, Space Bar, Left Arrow, Right Arrow, Up Arrow, Down Arrow

	ESC - exit program
	Space Bar - bowl ball
	"R"/"r" Key - reset game
	Left Arrow - move ball left along lane 
	Right Arrow - move ball right along lane
	Up Arrow - increase angle at which ball will be bowled
	Down Arrow - decrease angle at which ball will be bowled
\***********************************************************************************/

void processNormalKeys(unsigned char key, int x, int y) {
	if (key == 27){				//ESC key
		exit(0);				//Close program
	}
	if (key == 32){				//Space Bar
		bowlBall();				//Bowl ball
	}
	if (key == 82){				//"R" key
		newGame();				//New game
	}
	if (key == 114){			//"r" key
		newGame();				//New game
	}
}

void processSpecialKeys(int key, int xx, int yy) {
	float fraction = 0.1f;
	switch (key) {
		case GLUT_KEY_LEFT:					//Move ball left when left arrow key pressed
			if (hasBowled == false){
				if (ballX<-LANE_WIDTH){		//Ball position restricted by lane width
					break;
				}
				else ballX-=ballSpeedX;
				break;
			}
			else if (hasBowled==true){		//If already bowled, do not allow repositioning of ball along X axis
				break;
			}
		case GLUT_KEY_RIGHT :				//Move ball right when right arrow key pressed
			if (hasBowled == false){
				if (ballX>LANE_WIDTH){		//Ball position restricted by lane width
					break;
				}
				else ballX+=ballSpeedX;		
				break;
			}
			else if (hasBowled == true){	//If already bowled, do not allow repositioning of ball along X axis
				break;
			}
		case GLUT_KEY_UP :						//Increase angle of bowl
			if (ballAngle>= MAX_BALL_ANGLE){	
				ballAngle = MAX_BALL_ANGLE;		//Angle can not exceed maximum set angle of 0.005
			}
			else {
				ballAngle += 0.0001;			//Increase angle 
			}
			break;
		case GLUT_KEY_DOWN :					//Decrease angle of bowl
			if (ballAngle<=-MAX_BALL_ANGLE){
				ballAngle = -MAX_BALL_ANGLE;	//Angle can not exceed minimum set angle of -0.005
			}
			else {
				ballAngle -= 0.0001;			//Decrease angle
			}
			break;
	}
}

/***********************************************************************************\ 
							SUB MENU & HANDLING 
							(Right-Click Menu)
\***********************************************************************************/

//Create submenu on right-click:
void createSubMenu() {
	int menu;
	menu = glutCreateMenu(processMenuEvents);
	glutAddMenuEntry("New Game", 1);		//Add menu entry "New Game"
	glutAddMenuEntry("Exit", 2);			//Add menu entry "Exit"
	glutAttachMenu(GLUT_RIGHT_BUTTON);		//Attach the menu to right mouse button
}

void processMenuEvents(int option) {
	switch (option) {
	case 1 :		//New Game
		newGame();
		break;
	case 2:			//Exit Game
		exit(0);
		break;
	}
}

/***********************************************************************************\ 
							WINDOW RE-SIZING 
\***********************************************************************************/

void changeSize(int w, int h) {
	if (h == 0){								//Prevent a divide by zero, when window is too short
		h = 1;
	}
	float ratio =  w * 1.0 / h;
	glMatrixMode(GL_PROJECTION);				//Use the Projection Matrix
	glLoadIdentity();							//Reset Matrix
	glViewport(0, 0, w, h);						//Set the viewport to be the entire window
	gluPerspective(45.0f, ratio, 0.1f, 100.0f);	//Set the correct perspective
	glMatrixMode(GL_MODELVIEW);					//Return to the Modelview
}

/***********************************************************************************\ 
									MAIN  
				Initializes GLUT, generates frames, windows and menus,
					runs GLUT's main loop (for event processing) 
\***********************************************************************************/

int main(int argc, char **argv) {
	//Initialize GLUT and create window:
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100,100);
	glutInitWindowSize(WIN_WIDTH, WIN_HEIGHT);
	glutCreateWindow("BOWLING GAME - BLAIR TRUSLER");

	//Register callbacks:
	glutDisplayFunc(renderScene);
	glutReshapeFunc(changeSize);
	glutIdleFunc(renderScene);
	
	//Enable keyboard input:
	glutKeyboardFunc(processNormalKeys);
	glutSpecialFunc(processSpecialKeys);

	//Creates sub menu for right-click:
	createSubMenu();

	//Enable Z axis:
	glEnable(GL_DEPTH_TEST);

	//Initialize pin size:
	initPinSize();

	//New game is created:
	gameOver = false;

	//Initialize number of balls to bowl:
	NUM_OF_BALLS_BOWLED = 0;
	NUM_OF_PINS_HIT = 0; 

	//Enable GLUT event processing cycle:
	glutMainLoop();

	return 1;
}

